package net.entelijan.cobean.worker;

import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import net.entelijan.cobean.worker.api.ITaskCompanion;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.ClassUtils;

import edu.umd.cs.findbugs.annotations.SuppressWarnings;

public class LongRunningActionListener implements ActionListener {

	private static Log log = LogFactory.getLog(LongRunningActionListener.class);

	private Object bean;
	private IMethodFinder methodFinder;
	private Window owner;
	private IMessageHandler handler;

	public LongRunningActionListener(Window owner, Object bean,
			IMethodFinder methodFinder, IMessageHandler handler) {
		super();
		this.bean = bean;
		this.methodFinder = methodFinder;
		this.owner = owner;
		this.handler = handler;
	}

	public void actionPerformed(ActionEvent e) {
		log.info("[actionPerformed] calling method on " + bean + " using "
				+ methodFinder);
		Method validationMethod = methodFinder.findValidationMethod(bean);
		try {
			if (validationMethod != null) {
				validationMethod.invoke(bean);
				invokeDoMethod();
			} else {
				invokeDoMethod();
			}
		} catch (ValidationException e1) {
			handle(e1);
		} catch (InvocationTargetException e1) {
			handle(e1);
		} catch (Exception e1) {
			handleException(e1);
		} catch (Throwable e1) {
			handleThrowable(e1);
		}

	}

	private void invokeDoMethod() throws InvocationTargetException,
			IllegalArgumentException, IllegalAccessException {
		final Method doMethod = methodFinder.findDoMethod(bean);
		if (doMethod == null) {
			throw new IllegalStateException("Found no do method on " + bean
					+ " using " + methodFinder);
		}
		if (doMethod.getParameterTypes().length == 0) {
			doMethod.invoke(bean);
		} else if (hasCompanionParameter(doMethod)) {
			final RunDialog<Void> rd = new RunDialog<Void>(owner, doMethod
					.getName());

			final CobeanWorker<Void> worker = new CobeanWorker<Void>() {

				protected Void doInBackground(ITaskCompanion companion)
						throws Exception {
					doMethod.invoke(bean, rd);
					return null;
				}

			};
			worker.execute(rd);
			rd.setVisible(true);
			if (rd.getException() != null) {
				handler.handleError(getTheRealMessage(rd.getException()));
			}
		}
	}

	private String getTheRealMessage(Throwable ex) {
		String re;
		if (ex instanceof ExecutionException) {
			re = getTheRealMessage(ex.getCause());
		} else if (ex instanceof InvocationTargetException) {
			re = getTheRealMessage(ex.getCause());
		} else {
			String msg = ex.getMessage();
			if (msg == null || "".equals(msg)) {
				re = ex.toString();
			} else {
				re = msg;
			}
		}
		return re;
	}

	private void showError(String msg) {
		handler.handleError(msg);
	}

	private void showInfo(String msg) {
		handler.handleInfo(msg);
	}

	private boolean hasCompanionParameter(Method m) {
		return m.getParameterTypes().length == 1
				&& ClassUtils.isAssignable(ITaskCompanion.class, m
						.getParameterTypes()[0]);
	}

	@SuppressWarnings
	private void handleThrowable(Throwable e1) {
		String msg = "A fatal error occurred. The Program must be closed. "
				+ e1;
		log.error(msg, e1);
		showError(msg);
		System.exit(1);
	}

	private void handleException(Exception exception) {
		String msg = "Could not call method on " + bean + " using "
				+ methodFinder;
		log.error(msg, exception);
		showError(msg);
	}

	private void handle(InvocationTargetException itExc) {
		if (itExc.getCause() instanceof ValidationException) {
			handleValidationException(itExc);
		} else {
			String msg = "Could not call method on " + bean + " using "
					+ methodFinder;
			log.error(msg, itExc);
			showError(msg);
		}
	}

	private void handleValidationException(InvocationTargetException itExc) {
		String msg = itExc.getCause().getMessage();
		log.debug(msg, itExc);
		showInfo(msg);
	}

	private void handle(ValidationException e1) {
		String msg = e1.getMessage();
		log.debug(msg, e1);
		showInfo(msg);
	}

}
